package turritopsis

import (
    "bufio"
    "encoding/csv"
    "encoding/json"
    "errors"
    "fmt"
    "os"
    "path/filepath"
    "strconv"
    "strings"
)

// EpochPlan describes one epoch's dynamic parameters as per Turritopsis paper:
// - Members: optional explicit member id list for this epoch; if empty, N is used as size
// - N,F,L: process count, byzantine bound, churn bound for this epoch
//   Safety requires: n >= 3f + 2l + 1
type EpochPlan struct {
    Epoch   int   `json:"epoch"`
    Members []int `json:"members,omitempty"`
    N       int   `json:"n"`
    F       int   `json:"f"`
    L       int   `json:"l"`
}

// Validate checks the inequality n >= 3f + 2l + 1.
func (p *EpochPlan) Validate() error {
    n := p.N
    if len(p.Members) > 0 { n = len(p.Members) }
    if n <= 0 { return errors.New("invalid n") }
    if p.F < 0 || p.L < 0 { return errors.New("invalid f or l") }
    bound := 3*p.F + 2*p.L + 1
    if n < bound {
        return fmt.Errorf("inequality violated: n=%d < 3f+2l+1=%d (f=%d l=%d)", n, bound, p.F, p.L)
    }
    return nil
}

// LoadEpochPlans loads plans from JSON or CSV path.
// JSON format: array of EpochPlan
// CSV headers: epoch,n,f,l,members  (members as comma-separated ints)
func LoadEpochPlans(path string) ([]EpochPlan, error) {
    ext := strings.ToLower(filepath.Ext(path))
    switch ext {
    case ".json":
        var plans []EpochPlan
        b, err := os.ReadFile(path)
        if err != nil { return nil, err }
        if err := json.Unmarshal(b, &plans); err != nil { return nil, err }
        return plans, nil
    case ".csv":
        f, err := os.Open(path)
        if err != nil { return nil, err }
        defer f.Close()
        r := csv.NewReader(bufio.NewReader(f))
        rows, err := r.ReadAll()
        if err != nil { return nil, err }
        if len(rows) == 0 { return nil, errors.New("empty csv") }
        // expect header
        idx := map[string]int{}
        for i, h := range rows[0] { idx[strings.ToLower(strings.TrimSpace(h))] = i }
        get := func(row []string, key string) string {
            if j, ok := idx[key]; ok && j < len(row) { return strings.TrimSpace(row[j]) }
            return ""
        }
        var plans []EpochPlan
        for _, row := range rows[1:] {
            if len(strings.TrimSpace(strings.Join(row, ""))) == 0 { continue }
            var ep EpochPlan
            if v := get(row, "epoch"); v != "" { ep.Epoch, _ = strconv.Atoi(v) }
            if v := get(row, "n"); v != "" { ep.N, _ = strconv.Atoi(v) }
            if v := get(row, "f"); v != "" { ep.F, _ = strconv.Atoi(v) }
            if v := get(row, "l"); v != "" { ep.L, _ = strconv.Atoi(v) }
            if v := get(row, "members"); v != "" {
                parts := strings.Split(v, ",")
                for _, s := range parts { s=strings.TrimSpace(s); if s=="" {continue}; x, _ := strconv.Atoi(s); ep.Members = append(ep.Members, x) }
            }
            plans = append(plans, ep)
        }
        return plans, nil
    default:
        return nil, fmt.Errorf("unsupported plan file: %s", path)
    }
}





